package edu.northwestern.cbits.purple_robot_manager; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.UUID; import org.json.JSONException; import org.json.JSONObject; import android.app.AlarmManager; import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Vibrator; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import edu.northwestern.cbits.purple_robot_manager.activities.settings.SettingsKeys; import edu.northwestern.cbits.purple_robot_manager.config.LegacyJSONConfigFile; import edu.northwestern.cbits.purple_robot_manager.logging.LogManager; import edu.northwestern.cbits.purple_robot_manager.logging.SanityManager; import edu.northwestern.cbits.purple_robot_manager.probes.Probe; import edu.northwestern.cbits.purple_robot_manager.probes.builtin.ActivityDetectionProbe; import edu.northwestern.cbits.purple_robot_manager.scripting.BaseScriptEngine; import edu.northwestern.cbits.purple_robot_manager.triggers.TriggerManager; public class ManagerService extends IntentService { public static String RUN_SCRIPT_INTENT = "purple_robot_manager_run_script"; public static String RUN_SCRIPT = "run_script"; public static String APPLICATION_LAUNCH_INTENT = "purple_robot_manager_application_launch"; public static String APPLICATION_LAUNCH_INTENT_PACKAGE = "purple_robot_manager_widget_launch_package"; public static String APPLICATION_LAUNCH_INTENT_URL = "purple_robot_manager_widget_launch_url"; public static String APPLICATION_LAUNCH_INTENT_PARAMETERS = "purple_robot_manager_widget_launch_parameters"; public static String APPLICATION_LAUNCH_INTENT_POSTSCRIPT = "purple_robot_manager_widget_launch_postscript"; public static String UPDATE_WIDGETS = "purple_robot_manager_update_widgets"; public static String FIRE_TRIGGERS_INTENT = "purple_robot_manager_fire_triggers"; public static String INCOMING_DATA_INTENT = "purple_robot_manager_incoming_data"; public static String UPLOAD_LOGS_INTENT = "purple_robot_manager_upload_logs"; public static String REFRESH_ERROR_STATE_INTENT = "purple_robot_manager_refresh_errors"; public static String GOOGLE_PLAY_ACTIVITY_DETECTED = "purple_robot_manager_google_play_activity_detected"; public static String HAPTIC_PATTERN_INTENT = "purple_robot_manager_haptic_pattern"; public static String HAPTIC_PATTERN_NAME = "purple_robot_manager_haptic_pattern_name"; public static String HAPTIC_PATTERN_REPEATS = "purple_robot_manager_haptic_pattern_repeats"; public static String RINGTONE_STOP_INTENT = "purple_robot_manager_stop_ringtone"; public static String RINGTONE_INTENT = "purple_robot_manager_ringtone"; public static String RINGTONE_NAME = "purple_robot_manager_ringtone_name"; public static String RINGTONE_LOOPS = "purple_robot_manager_ringtone_loops"; public static String REFRESH_CONFIGURATION = "purple_robot_manager_refresh_configuration"; public static final String UPDATE_TRIGGER_SCHEDULE_INTENT = "purple_robot_manager_update_trigger_schedule"; public static long startTimestamp = System.currentTimeMillis(); private static boolean _checkSetup = false; private static long _lastTriggerNudge = 0; private static boolean _updatingTriggerSchedule = false; protected static boolean _needsTriggerUpdate = false; private static boolean _looping = false; protected static MediaPlayer _player = null; private static boolean _vibrationRepeats = false; public ManagerService() { super("ManagerService"); } public ManagerService(String name) { super(name); } @Override @SuppressWarnings("deprecation") protected void onHandleIntent(final Intent intent) { String action = intent.getAction(); final ManagerService me = this; if (INCOMING_DATA_INTENT.equalsIgnoreCase(action)) { Bundle data = intent.getBundleExtra(Probe.PROBE_DATA); UUID uuid = UUID.randomUUID(); data.putString(Probe.PROBE_GUID, uuid.toString()); LocalBroadcastManager localManager = LocalBroadcastManager.getInstance(this); Intent probeIntent = new Intent(edu.northwestern.cbits.purple_robot_manager.probes.Probe.PROBE_READING); probeIntent.putExtras(data); localManager.sendBroadcast(probeIntent); } else if (GOOGLE_PLAY_ACTIVITY_DETECTED.equalsIgnoreCase(action)) ActivityDetectionProbe.activityDetected(this, intent); else if (REFRESH_ERROR_STATE_INTENT.equalsIgnoreCase(action)) { Runnable r = new Runnable() { @Override public void run() { SanityManager.getInstance(me).refreshState(); } }; Thread t = new Thread(r); t.start(); } else if (UPDATE_WIDGETS.equalsIgnoreCase(action)) { Intent broadcast = new Intent("edu.northwestern.cbits.purple.UPDATE_WIDGET"); broadcast.putExtras(intent.getExtras()); this.startService(broadcast); } else if (HAPTIC_PATTERN_INTENT.equalsIgnoreCase(action)) { String pattern = intent.getStringExtra(HAPTIC_PATTERN_NAME); ManagerService._vibrationRepeats = intent.getBooleanExtra(ManagerService.HAPTIC_PATTERN_REPEATS, false); if (!pattern.startsWith("vibrator_")) pattern = "vibrator_" + pattern; int[] patternSpec = this.getResources().getIntArray(R.array.vibrator_buzz); if ("vibrator_blip".equalsIgnoreCase(pattern)) patternSpec = this.getResources().getIntArray(R.array.vibrator_blip); if ("vibrator_sos".equalsIgnoreCase(pattern)) patternSpec = this.getResources().getIntArray(R.array.vibrator_sos); final long[] longSpec = new long[patternSpec.length]; for (int i = 0; i < patternSpec.length; i++) { longSpec[i] = (long) patternSpec[i]; } final Vibrator v = (Vibrator) this.getSystemService(Context.VIBRATOR_SERVICE); Runnable r = new Runnable() { public void run() { do { long duration = 0; for (long length : longSpec) duration += length; v.cancel(); v.vibrate(longSpec, -1); try { Thread.sleep(duration + 250); } catch (InterruptedException e) { } } while(ManagerService._vibrationRepeats); } }; Thread t = new Thread(r); t.start(); } else if (RINGTONE_STOP_INTENT.equalsIgnoreCase(action)) { ManagerService._looping = false; if (ManagerService._player != null) ManagerService._player.stop(); ManagerService._player = null; } else if (RINGTONE_INTENT.equalsIgnoreCase(action)) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); Uri toneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); String toneString = prefs.getString(SettingsKeys.RINGTONE_KEY, null); String name = null; if (intent.hasExtra(ManagerService.RINGTONE_NAME)) { name = intent.getStringExtra(ManagerService.RINGTONE_NAME); toneString = null; toneUri = null; } try { if (toneString != null) { if (toneString.equals("0")) toneUri = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_NOTIFICATION); else if (toneString.startsWith("sounds/") == false) toneUri = Uri.parse(toneString); else { toneUri = null; name = toneString; } } else { if (name != null) { RingtoneManager rm = new RingtoneManager(this); rm.setType(RingtoneManager.TYPE_NOTIFICATION); Cursor cursor = rm.getCursor(); do { String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX); if (name.equalsIgnoreCase(title)) toneUri = rm.getRingtoneUri(cursor.getPosition()); } while (cursor.moveToNext()); cursor.deactivate(); } } } catch (Exception e) { e.printStackTrace(); LogManager.getInstance(this).logException(e); } if (toneUri != null) { final Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), toneUri); ManagerService._looping = intent.getBooleanExtra(ManagerService.RINGTONE_LOOPS, false); if (r != null) { Thread t = new Thread(new Runnable() { @Override public void run() { r.play(); try { while (r.isPlaying()) Thread.sleep(100); if (ManagerService._looping) { Thread t = new Thread(this); t.start(); } } catch (InterruptedException e) { LogManager.getInstance(me).logException(e); } } }); t.start(); } } else { try { String path = ManagerService.pathForSound(this, name); if (path != null) { final AssetFileDescriptor afd = this.getAssets().openFd(path); Runnable r = new Runnable() { @Override public void run() { ManagerService._player = new MediaPlayer(); try { ManagerService._player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); ManagerService._player.setLooping(intent.getBooleanExtra(ManagerService.RINGTONE_LOOPS, false)); ManagerService._player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); ManagerService._player.prepare(); ManagerService._player.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer player) { ManagerService._player = null; try { afd.close(); } catch (IOException e) { LogManager.getInstance(me).logException(e); } } }); ManagerService._player.start(); } catch (IllegalArgumentException | IOException | IllegalStateException e) { LogManager.getInstance(me).logException(e); } } }; Thread t = new Thread(r); t.start(); } } catch (IOException e) { e.printStackTrace(); LogManager.getInstance(this).logException(e); Intent defaultIntent = new Intent(ManagerService.RINGTONE_INTENT); this.startService(defaultIntent); } } } else if (APPLICATION_LAUNCH_INTENT.equalsIgnoreCase(action)) { if (intent.hasExtra(APPLICATION_LAUNCH_INTENT_PACKAGE)) { String packageName = intent.getStringExtra(APPLICATION_LAUNCH_INTENT_PACKAGE); if (packageName != null) { Intent launchIntent = this.getPackageManager().getLaunchIntentForPackage(packageName); if (launchIntent != null) { String launchParams = intent.getStringExtra(APPLICATION_LAUNCH_INTENT_PARAMETERS); if (launchParams != null) { try { JSONObject paramsObj = new JSONObject(launchParams); Iterator<String> keys = paramsObj.keys(); while (keys.hasNext()) { String key = keys.next(); launchIntent.putExtra(key, paramsObj.getString(key)); } } catch (JSONException e) { LogManager.getInstance(this).logException(e); } } this.startActivity(launchIntent); } String script = intent.getStringExtra(APPLICATION_LAUNCH_INTENT_POSTSCRIPT); if (script != null) BaseScriptEngine.runScript(this, script); } } else if (intent.hasExtra(APPLICATION_LAUNCH_INTENT_URL)) { String url = intent.getStringExtra(APPLICATION_LAUNCH_INTENT_URL); if (url != null) { Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); this.startActivity(launchIntent); String script = intent.getStringExtra(APPLICATION_LAUNCH_INTENT_POSTSCRIPT); if (script != null) BaseScriptEngine.runScript(this, script); } } } else if (RUN_SCRIPT_INTENT.equalsIgnoreCase(action)) { if (intent.hasExtra(RUN_SCRIPT)) { String script = intent.getStringExtra(RUN_SCRIPT); if (script != null) BaseScriptEngine.runScript(this, script); } } else if (FIRE_TRIGGERS_INTENT.equals(action)) { TriggerManager.getInstance(this).nudgeTriggers(this); Intent updateIntent = new Intent(ManagerService.UPDATE_TRIGGER_SCHEDULE_INTENT); updateIntent.setClass(this, ManagerService.class); this.startService(updateIntent); } else if (UPDATE_TRIGGER_SCHEDULE_INTENT.equals(action)) { Runnable r = new Runnable() { @Override public void run() { if (ManagerService._updatingTriggerSchedule) { ManagerService._needsTriggerUpdate = true; return; } ManagerService._updatingTriggerSchedule = true; ManagerService._needsTriggerUpdate = false; List<Long> times = TriggerManager.getInstance(me).upcomingFireTimes(me); long now = System.currentTimeMillis(); for (int index = 0; now > ManagerService._lastTriggerNudge && index < times.size(); index++) { Long fire = times.get(index); if (Math.abs(ManagerService._lastTriggerNudge - fire) >= 60000) { AlarmManager alarmManager = (AlarmManager) me.getSystemService(Context.ALARM_SERVICE); PendingIntent pi = PendingIntent.getService(me, 0, new Intent(ManagerService.FIRE_TRIGGERS_INTENT), PendingIntent.FLAG_UPDATE_CURRENT); alarmManager.cancel(pi); ManagerService._lastTriggerNudge = fire; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) alarmManager.setExact(AlarmManager.RTC_WAKEUP, fire, pi); else alarmManager.set(AlarmManager.RTC_WAKEUP, fire, pi); break; } } ManagerService._updatingTriggerSchedule = false; if (ManagerService._needsTriggerUpdate) this.run(); } }; Thread t = new Thread(r); t.start(); } else if (REFRESH_CONFIGURATION.equals(action)) LegacyJSONConfigFile.update(this, false); } public static void setupPeriodicCheck(final Context context) { if (ManagerService._checkSetup) return; SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PendingIntent pi = PendingIntent.getService(context, 0, new Intent(ManagerService.REFRESH_CONFIGURATION), PendingIntent.FLAG_UPDATE_CURRENT); alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 60000, pi); pi = PendingIntent.getService(context, 0, new Intent(ManagerService.UPDATE_TRIGGER_SCHEDULE_INTENT), PendingIntent.FLAG_UPDATE_CURRENT); alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), (15 * 60 * 1000), pi); prefs.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { Intent reloadIntent = new Intent(ManagerService.REFRESH_CONFIGURATION); reloadIntent.setClass(context, ManagerService.class); context.startService(reloadIntent); } }); Intent nudgeIntent = new Intent(PersistentService.NUDGE_PROBES); nudgeIntent.setClass(context, PersistentService.class); context.startService(nudgeIntent); SanityManager.getInstance(context); ManagerService._checkSetup = true; } public static String soundNameForPath(Context context, String path) { String[] values = context.getResources().getStringArray(R.array.sound_effect_values); String[] labels = context.getResources().getStringArray(R.array.sound_effect_labels); for (int i = 0; i < labels.length && i < values.length; i++) { if (values[i].toLowerCase(Locale.ENGLISH).equals(path.toLowerCase(Locale.getDefault()))) return labels[i]; } return path; } private static String pathForSound(Context context, String name) { String[] values = context.getResources().getStringArray(R.array.sound_effect_values); String[] labels = context.getResources().getStringArray(R.array.sound_effect_labels); if (name != null) { for (int i = 0; i < labels.length && i < values.length; i++) { if (labels[i].toLowerCase(Locale.getDefault()).equals(name.toLowerCase(Locale.getDefault()))) return values[i]; } for (int i = 0; i < labels.length && i < values.length; i++) { if (labels[i].toLowerCase(Locale.getDefault()).endsWith(name.toLowerCase(Locale.getDefault()))) return values[i]; } } return name; } public static void resetTriggerNudgeDate() { ManagerService._lastTriggerNudge = 0; } public static void stopAllVibrations() { ManagerService._vibrationRepeats = false; } }